package cn.lingyangwl.agile.infra.file.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.RandomUtil;
import cn.lingyangwl.agile.common.security.annotation.*;
import cn.lingyangwl.agile.infra.file.channel.*;
import cn.lingyangwl.agile.infra.file.constant.*;
import cn.lingyangwl.agile.infra.file.modle.rqrs.*;
import cn.lingyangwl.agile.infra.file.service.*;
import cn.lingyangwl.agile.infra.file.utils.*;
import cn.lingyangwl.agile.infra.module.file.*;
import cn.lingyangwl.agile.infra.module.file.manager.*;
import cn.lingyangwl.agile.infra.module.file.model.entity.*;
import cn.lingyangwl.agile.infra.module.file.model.rqrs.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.agile.ram.*;
import cn.lingyangwl.framework.core.response.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;

/**
 * @author shenguangyang
 * @since 1.0.0
 */
@Slf4j
@Validated
@RestController
@RequestMapping("/file")
public class FileCommonController {
    private final String fileDir = "/opt/upload/";
    private final String demoDir = "demo";
    private final String demoPath = demoDir + "/";

    @Resource
    private FileRecordService fileRecordService;

    @Resource
    private FileProperties fileProperties;

    @Resource
    private FileManager fileManager;

    @Resource
    private StorageChannelManager channelManager;
    @Resource
    private FileConfigService fileConfigService;

    /**
     * 获取内网url
     */
    @InnerAuth
    @GetMapping(value = "/getIntranetUrl")
    public Resp<String> getIntranetUrl() {
        return Resp.ok(fileProperties.getIntranetUrl());
    }

    /**
     * 将虚拟url转成真实url, 直接俄将虚拟url返回接口, 因为网关会进行统一处理, 所以这个接口只对走网关的请求有效
     */
    @GetMapping(value = "/virtualToRealBaseUrl")
    public Resp<String> virtualToRealBaseUrl(@RequestParam("virtualUrl") String virtualUrl) {
        return Resp.ok(virtualUrl);
    }

    /**
     * 上传文件到本地
     */
    @PostMapping("/uploadToLocal")
    public Resp<?> fileUploadToLocal(@RequestParam("file") MultipartFile file) {
        fileRecordService.uploadCheck(file);
        String fileName = FileUtils.getFileName(file);
        log.info("上传文件：{}", fileDir + demoPath + fileName);
        // 判断是否存在同名文件
        if (existsFile(fileName)) {
            return Resp.fail("存在同名文件，请先删除原有文件再次上传");
        }

        File outFile = new File(fileDir + demoPath);
        if (!outFile.exists() && !outFile.mkdirs()) {
            log.error("创建文件夹【{}】失败，请检查目录权限！", fileDir + demoPath);
        }

        try (InputStream in = file.getInputStream();
             OutputStream out = Files.newOutputStream(Paths.get(fileDir + demoPath + fileName))) {
            StreamUtils.copy(in, out);
            return Resp.ok();
        } catch (IOException e) {
            log.error("文件上传失败", e);
            return Resp.fail("文件上传失败");
        }
    }

    /**
     * 下载文件
     */
    @RamResource(action = "file:common:download", name = "下载文件", type = ActionType.READ)
    @GetMapping("/download")
    public void download(HttpServletResponse response,  @RequestParam("id") Long recordId) throws Exception {
        fileRecordService.download(response, recordId);
    }

    /**
     * 上传文件到文件服务器
     */
    @RamResource(action = "file:common:upload", name = "上传文件", type = ActionType.WRITE)
    @PostMapping("/upload")
    public Resp<?> fileUpload(@Validated UploadFileReq req) throws Exception {
        try (InputStream in = req.getFile().getInputStream()) {
            FileRecordSaveResp record = fileRecordService.genFileRecord(req);
            String objectName = record.getFileRecord().getFilePath();
            IStorageChannel storageChannel = channelManager.getStorageChannel();
            storageChannel.uploadFile(in, req.getFile().getContentType(), objectName);
            String fileUrl = fileManager.getVirtualFileUrl(objectName);
            fileUrl = fileUrl + "?tenantId=" + TenantContext.get().getTenantId();
            // message返回空, 前端会取data中的值, 来展示上传的文件名且会赋值到表单中
            return Resp.ok(fileUrl, fileUrl);
        }
    }

    /**
     * 上传文件到文件服务器
     */
    @RamResource(action = "file:common:upload", name = "上传文件", type = ActionType.WRITE)
    @PostMapping("/uploadBase64")
    public Resp<?> uploadBase64(@RequestBody @Validated UploadBase64Req req) throws Exception {

        String fileName = RandomUtil.randomString(16);

        log.info("上传文件：{}", fileDir + demoPath + fileName);

        FileConfig fileConfig = fileConfigService.getFileConfig(new FileConfigGetReq().setBizType(req.getBizType()));
        if (Objects.isNull(fileConfig)) {
            throw new BizException("尚未找到关联的文件配置");
        }
        Base64TypeEnum base64TypeEnum = Base64TypeEnum.ofByBase64(req.getBase64());
        String ext = base64TypeEnum.getExt();
        String objectName = fileConfig.getPath() + "/" + fileName + ext;

        byte[] decode = Base64.decode(req.getBase64().replaceFirst(base64TypeEnum.getPre() + ",", ""));

        Long fileSizeLimit = fileConfig.getFileSizeLimit();
        if (Objects.nonNull(fileSizeLimit) && fileSizeLimit.compareTo((long) (decode.length / 1024)) < 0) {
            throw new BizException("文件太大, 最大为 {} kb", fileSizeLimit);
        }

        IStorageChannel storageChannel = channelManager.getStorageChannel();
        storageChannel.uploadFile(IoUtil.toStream(decode), base64TypeEnum.getContentType(), objectName);
        String fileUrl = fileManager.getVirtualFileUrl(objectName);
        fileUrl = fileUrl + "?tenantId=" + TenantContext.get().getTenantId();
        return Resp.ok(fileUrl, fileUrl);
    }

    @GetMapping("/deleteFileOfLocal")
    public Resp<Object> deleteFile(String fileName) {
        if (fileName == null || fileName.length() == 0) {
            return Resp.fail("文件名为空，删除失败！");
        }
        try {
            fileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (fileName.contains("/")) {
            fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
        }
        if (FileUtils.isIllegalFileName(fileName)) {
            return Resp.fail("非法文件名，删除失败！");
        }
        File file = new File(fileDir + demoPath + fileName);
        log.info("删除文件：{}", file.getAbsolutePath());
        if (file.exists() && !file.delete()) {
            String msg = String.format("删除文件【%s】失败，请检查目录权限！", file.getPath());
            log.error(msg);
            return Resp.fail(msg);
        }
        return Resp.ok();
    }

    private boolean existsFile(String fileName) {
        File file = new File(fileDir + demoPath + fileName);
        return file.exists();
    }
}
